/**
 * Copyright ©2016-2020 northkingbpo Corporation, All Rights Reserved
 * http://www.northkingbpo.net/
 */
package profile.hujia.algorithm.iteration;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import profile.hujia.algorithm.common.Constant;
import profile.hujia.algorithm.simulate.ConstraintCondition;

/**
 * class info
 *
 * @author hujia
 * @date 2019-02-14 16:10
 */
@Data
@Slf4j
public class InitialOptimization {

    private ConstraintCondition constraintCondition;
    private int[] timeNumberOfPeopleForEachKey;
    private int[] mapRelation;
    private int predistributionNumber;

    public InitialOptimization(ConstraintCondition constraintCondition) {
        this.constraintCondition = constraintCondition;
        timeNumberOfPeopleForEachKey = new int[this.constraintCondition.getPeopleNumber()];
    }

    private void firstDistribute() {
        do {
            do {
                addVirtualPeople();
            } while (diff());
        } while (findOnePeople());
    }

    public void run() {
        do {
            do {
                firstDistribute();
            } while (findMinimumCapacityPeople(1));
        } while (equalDistribute());
    }

    private boolean equalDistribute() {
        //find each key
        for (int i = 0; i < getConstraintCondition().getKeyNumber(); ++i) {
            if (getConstraintCondition().getNumberOfTaskForEachKey()[i] > 0) {
                if (getConstraintCondition().getNumberOfPeopleForEachKey()[i] <= 0) {
                    log.error("data error");
                }
                int pos = 0;
                int task, people;
                do {
                    int tail = getConstraintCondition().getNumberOfTaskForEachKey()[i];
                    task = getConstraintCondition().getKeyOfTaskSpecific()[i][tail - 1];
                    people = getConstraintCondition().getKeyOfPeopleSpecific()[i][pos];
                    pos = (pos + 1) % getConstraintCondition().getNumberOfPeopleForEachKey()[i];
                } while (distributeTaskToPeople(task, people, i) &&
                        getConstraintCondition().getNumberOfTaskForEachKey()[i] > 0);
                if (getConstraintCondition().getNumberOfTaskForEachKey()[i] == 0) {
                    removeThisKeyOfAllPeople(i);
                }

                return true;
            }
        }
        return false;
    }

    private void removeThisKeyOfAllPeople(int key) {
        for (int i = 0; i < constraintCondition.getNumberOfPeopleForEachKey()[key]; ++i) {
            int people = constraintCondition.getKeyOfPeopleSpecific()[key][i];
            if (people == 0) {
                log.error("error");
            }
            int len = getConstraintCondition().getNumberOfPeopleAbility()[people - 1];
            for (int j = 0; j < len; ++j) {
                if (getConstraintCondition().getPeopleAbilitySpecific()[people - 1][j] == key) {
                    getConstraintCondition().getPeopleAbilitySpecific()[people - 1][j] =
                            getConstraintCondition().getPeopleAbilitySpecific()[people - 1][len - 1];
                    break;
                }
            }
            --getConstraintCondition().getNumberOfPeopleAbility()[people - 1];
        }
    }


    private void addVirtualPeople() {
        for (int key = 0; key < this.getConstraintCondition().getKeyNumber(); ++key) {
            int sum = 0;
            for (int j = 0; j < this.getConstraintCondition().getNumberOfPeopleForEachKey()[key]; ++j) {
                int people = this.getConstraintCondition().getKeyOfPeopleSpecific()[key][j];
                int peopleCapacity = this.getConstraintCondition().getPeopleCapacity()[people - 1];
                sum += peopleCapacity;
            }
            timeNumberOfPeopleForEachKey[key] = sum;
        }
    }

    private boolean findOnePeople() {
        for (int i = 0; i < getConstraintCondition().getKeyNumber(); ++i) {
            if (getConstraintCondition().getNumberOfPeopleForEachKey()[i] == 1) {
                log.info("the key is " + i + " have only one person. ");
                int people = getConstraintCondition().getKeyOfPeopleSpecific()[i][0];
                log.info("the people is " + people);
                boolean flag = true;
                while (flag && getConstraintCondition().getNumberOfTaskForEachKey()[i] > 0) {
                    int tail = getConstraintCondition().getNumberOfTaskForEachKey()[i];
                    int task = getConstraintCondition().getKeyOfTaskSpecific()[i][tail - 1];
                    flag = distributeTaskToPeople(task, people, i);
                }
                while (getConstraintCondition().getNumberOfTaskForEachKey()[i] > 0) {
                    int tail = getConstraintCondition().getNumberOfTaskForEachKey()[i];
                    int task = getConstraintCondition().getKeyOfTaskSpecific()[i][tail - 1];
                    distributeTaskToPeople(task, Constant.TASK_UNDISTRIBUTED, i);
                }
                removeThisKeyOfAllPeople(i);
                //task is empty
                getConstraintCondition().getNumberOfTaskForEachKey()[i] = 0;
                //because task is empty,so the people should set zero
                getConstraintCondition().getNumberOfPeopleForEachKey()[i] = 0;
                return true;
            }
        }
        return false;
    }

    private boolean findMinimumCapacityPeople(int num) {
        boolean isFind = false;
        for (int i = 0; i < getConstraintCondition().getPeopleNumber(); ++i) {
            if (getConstraintCondition().getNumberOfPeopleAbility()[i] == num) {
                //distributeByTask task to this people
                isFind = true;
                log.info("find the people is " + (i + 1) + " have " + num + " ability");
                boolean flag = true;
                while (getConstraintCondition().getNumberOfPeopleAbility()[i] > 0 && flag) {
                    int len = getConstraintCondition().getNumberOfPeopleAbility()[i];
                    int key = getConstraintCondition().getPeopleAbilitySpecific()[i][0];
                    while (getConstraintCondition().getNumberOfTaskForEachKey()[key] > 0) {
                        int task = getConstraintCondition().getKeyOfTaskSpecific()[key][0];
                        int length = getConstraintCondition().getNumberOfTaskForEachKey()[key];
                        distributeTaskToPeople(task, i + 1, key);
                        getConstraintCondition().getKeyOfTaskSpecific()[key][0] = getConstraintCondition().
                                getKeyOfTaskSpecific()[key][length - 1];
                        if (getConstraintCondition().getNumberOfPeopleAbility()[i] <= 0) {
                            flag = false;
                            break;
                        }
                    }
                    if (getConstraintCondition().getNumberOfTaskForEachKey()[key] == 0) {
                        removeThisKeyOfAllPeople(key);
                    }
                }
            }
        }
        return isFind;
    }

    private int findTaskMoreThanPeople() {
        for (int key = 0; key < this.getConstraintCondition().getKeyNumber(); ++key) {
            if (getConstraintCondition().getNumberOfTaskForEachKey()[key] - timeNumberOfPeopleForEachKey[key] >= 0
                    && getConstraintCondition().getNumberOfTaskForEachKey()[key] != 0) {
                return key;
            }
        }
        return -1;
    }

    private void distributeByTask(int key) {
        //distributeByTask task to all people for this key
        int len = getConstraintCondition().getNumberOfPeopleForEachKey()[key];
        while (len > 0) {
            int people = getConstraintCondition().getKeyOfPeopleSpecific()[key][0];
            int capacity = getConstraintCondition().getPeopleCapacity()[people - 1];
            for (int k = 0; k < capacity; ++k) {
                int tail = getConstraintCondition().getNumberOfTaskForEachKey()[key];
                int task = getConstraintCondition().getKeyOfTaskSpecific()[key][tail - 1];
                distributeTaskToPeople(task, people, key);
            }
            len--;

        }
        while (getConstraintCondition().getNumberOfTaskForEachKey()[key] > 0) {
            int tail = getConstraintCondition().getNumberOfTaskForEachKey()[key];
            int task = getConstraintCondition().getKeyOfTaskSpecific()[key][tail - 1];
            distributeTaskToPeople(task, Constant.TASK_UNDISTRIBUTED, key);
        }
        getConstraintCondition().getNumberOfTaskForEachKey()[key] = 0;
    }

    private boolean distributeTaskToPeople(int task, int people, int key) {

        if (people != Constant.TASK_UNDISTRIBUTED) {
            //people capacity - 1
            if (getConstraintCondition().getPeopleCapacity()[people - 1] > 0 &&
                    getConstraintCondition().getNumberOfPeopleForEachKey()[key] > 0 &&
                    getConstraintCondition().getNumberOfTaskForEachKey()[key] > 0) {
                --getConstraintCondition().getPeopleCapacity()[people - 1];
                --getConstraintCondition().getNumberOfTaskForEachKey()[key];
            } else {
                return false;
            }
            getConstraintCondition().getTaskDistributeRecord().put(task, people);
            log.info(String.format("task %d distribute to people %d", task, people));

            //judge the people capacity if zero, remove people from all queue
            if (getConstraintCondition().getPeopleCapacity()[people - 1] == 0) {
                removeOtherKeyOfThisPeople(people);
                setThisPeopleAbilityZero(people);
            }
        } else {
            --getConstraintCondition().getNumberOfTaskForEachKey()[key];
            getConstraintCondition().getTaskCanNotDistribute().put(task, people);
            log.info(String.format("task %d can not distribute ", task));
        }
        getConstraintCondition().getConstraintConditionSpecific()[task] = new int[1];
        getConstraintCondition().getConstraintConditionSpecific()[task][0] = people;
        return true;
    }

    private void setThisPeopleAbilityZero(int people) {
        getConstraintCondition().getNumberOfPeopleAbility()[people - 1] = 0;
    }

    private boolean diff() {
        int key = findTaskMoreThanPeople();
        if (key < 0) {
            return false;
        }
        log.info("the key is " + key + " task more than people .");
        distributeByTask(key);
        return true;
    }

    private void removeOtherKeyOfThisPeople(int people) {
        //length of the people which the task specific
        int len = getConstraintCondition().getNumberOfPeopleAbility()[people - 1];
        for (int i = 0; i < len; ++i) {
            // get all key from this people
            int key = getConstraintCondition().getPeopleAbilitySpecific()[people - 1][i];
            // get the key queue of people
            int length = getConstraintCondition().getNumberOfPeopleForEachKey()[key];
            for (int j = 0; j < length; ++j) {
                // remove this people form this key queue
                if (getConstraintCondition().getKeyOfPeopleSpecific()[key][j] == people) {
                    // swap the last people
                    getConstraintCondition().getKeyOfPeopleSpecific()[key][j] =
                            getConstraintCondition().getKeyOfPeopleSpecific()[key][length - 1];
                    break;
                }
            }
            --getConstraintCondition().getNumberOfPeopleForEachKey()[key];
        }
    }
}
